package com.small.os.minio.util;

 import com.small.os.minio.config.MinioConfig;
 import com.small.os.minio.entity.AqUpload;

 import io.minio.*;
 import io.minio.http.Method;
 import io.minio.messages.Item;
 import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wangguochao
 */
@Slf4j
@Component
public class MinIoUtils implements InitializingBean {

    @Autowired
    private MinioConfig minioConfig;

    private static String url;

    private static String accessKey;

    private static String secretKey;

    private static String bucketValue;

    private static String chunkBucKet;

    private static MinioClient minioClient;

    /**
     * 排序
     */
    public final static boolean SORT = true;
    /**
     * 不排序
     */
    public final static boolean NOT_SORT = false;
    /**
     * 默认过期时间(分钟)
     */
    private final static Integer DEFAULT_EXPIRY = 60;

    @Override
    public void afterPropertiesSet() throws Exception {
        minioClient = MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
        //方便管理分片文件，则单独创建一个分片文件的存储桶
        if (!isBucketExist(chunkBucKet)) {
            createBucket(chunkBucKet);
        }
    }

    /**
     * 初始化MinIo对象
     * 非此工具类请勿使用该方法
     */
    @PostConstruct
    public void init() {
        url = minioConfig.getUrl();
        accessKey = minioConfig.getAccessKey();
        secretKey = minioConfig.getSecretKey();
        bucketValue = minioConfig.getBucketName();
        chunkBucKet = "chunk";
    }

    /**
     * 文件是否存在
     *
     * @param bucketName 存储桶名称
     * @return true/false
     */
    public static boolean statObject(String bucketName) {
        try {
            return minioClient.isObjectLegalHoldEnabled(
                    IsObjectLegalHoldEnabledArgs.builder().bucket(bucketValue).object(bucketName).build());
        } catch (Exception e) {
            return false;
        }

    }

    /**
     * 存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return true/false
     */
    @SneakyThrows
    public static boolean isBucketExist(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     * @return true/false
     */
    @SneakyThrows
    public static boolean createBucket(String bucketName) {
        minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        return true;
    }

    /**
     * 获取访问对象的外链地址
     *
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @return viewUrl
     */
    @SneakyThrows
    public static String getObjectUrl(String bucketName, String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expiry)
                        .build()
        );
    }

    /**
     * 获取访问对象的外链地址
     *
     * @param objectName 对象名称
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @return viewUrl
     */
    @SneakyThrows
    public static String getObjectUrl(String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketValue)
                        .object(objectName)
                        .expiry(expiry)
                        .build()
        );
    }

    /**
     * 创建上传文件对象的外链
     *
     * @param bucketName 存储桶名称
     * @param objectName 欲上传文件对象的名称
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @return uploadUrl
     */
    @SneakyThrows
    public static String createUploadUrl(String bucketName, String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expiry)
                        .build()
        );
    }

    /**
     * 创建上传文件对象的外链
     *
     * @param bucketName 存储桶名称
     * @param objectName 欲上传文件对象的名称
     * @return uploadUrl
     */
    public static String createUploadUrl(String bucketName, String objectName) {
        return createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY);
    }

    /**
     * 批量创建分片上传外链
     *
     * @param bucketName 存储桶名称
     * @param objectMD5  欲上传分片文件主文件的MD5
     * @param chunkCount 分片数量
     * @return uploadChunkUrls
     */
    public static List<String> createUploadChunkUrlList(String bucketName, String objectMD5, Integer chunkCount) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == objectMD5) {
            return null;
        }
        objectMD5 += "/";
        if (null == chunkCount || 0 == chunkCount) {
            return null;
        }
        List<String> urlList = new ArrayList<>(chunkCount);
        for (int i = 1; i <= chunkCount; i++) {
            String objectName = objectMD5 + i + ".chunk";
            urlList.add(createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY));
        }
        return urlList;
    }

    /**
     * 创建指定序号的分片文件上传外链
     *
     * @param bucketName 存储桶名称
     * @param objectMD5  欲上传分片文件主文件的MD5
     * @param partNumber 分片序号
     * @return uploadChunkUrl
     */
    public static String createUploadChunkUrl(String bucketName, String objectMD5, Integer partNumber) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == objectMD5) {
            return null;
        }
        objectMD5 += "/" + partNumber + ".chunk";
        return createUploadUrl(bucketName, objectMD5, DEFAULT_EXPIRY);
    }

    /**
     * 获取对象文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param prefix     对象名称前缀
     * @param sort       是否排序(升序)
     * @return objectNames
     */
    @SneakyThrows
    public static List<String> listObjectNames(String bucketName, String prefix, Boolean sort) {
        ListObjectsArgs listObjectsArgs;
        if (null == prefix) {
            listObjectsArgs = ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .recursive(true)
                    .build();
        } else {
            listObjectsArgs = ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .recursive(true)
                    .build();
        }
        Iterable<Result<Item>> chunks = minioClient.listObjects(listObjectsArgs);
        List<String> chunkPaths = new ArrayList<>();
        for (Result<Item> item : chunks) {
            chunkPaths.add(item.get().objectName());
        }
        List<String> collect = chunkPaths.stream().sorted(Comparator.comparingInt(String::length).thenComparing(String::compareTo)).collect(Collectors.toList());
        if (sort) {
            return collect;
        }
        return collect;
    }

    /**
     * 获取对象文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param prefix     对象名称前缀
     * @return objectNames
     */
    public static List<String> listObjectNames(String bucketName, String prefix) {
        return listObjectNames(bucketName, prefix, SORT);
    }

    /**
     * 获取对象文件名称列表
     *
     * @param prefix 对象名称前缀
     * @return objectNames
     */
    public static List<String> listObjectNames(String prefix) {
        return listObjectNames(bucketValue, prefix, SORT);
    }

    /**
     * 获取对象文件名称列表
     *
     * @param prefix 对象名称前缀
     * @return objectNames
     */
    @SneakyThrows
    public static List<AqUpload> findByPath(String prefix) {
        ListObjectsArgs listObjectsArgs;
        if (null == prefix) {
            listObjectsArgs = ListObjectsArgs.builder()
                    .bucket(bucketValue)
                    .recursive(false)
                    .build();
        } else {
            listObjectsArgs = ListObjectsArgs.builder()
                    .bucket(bucketValue)
                    .prefix(prefix)
                    .recursive(false)
                    .build();
        }
        Iterable<Result<Item>> chunks = minioClient.listObjects(listObjectsArgs);
        List<AqUpload> list = new ArrayList<>();
        for (Result<Item> item : chunks) {
            Item item1 = item.get();
            if (item1 == null) {
                break;
            }
            AqUpload aqUpload = new AqUpload();
            aqUpload.setFileMd5(item1.objectName());
            if (!item1.isDir()) {
                String url = getObjectUrl(bucketValue, item1.objectName(), 720);
                aqUpload.setFilePath(url);
                ZonedDateTime dateTime = item1.lastModified();
                if (dateTime != null) {
                    aqUpload.setLastModified(dateTime.toLocalDateTime());
                }
            }
            long size = item1.size();
            list.add(aqUpload);
        }
        return list;
    }

    /**
     * 获取分片文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param ObjectMd5  对象Md5
     * @return objectChunkNames
     */
    public static List<String> listChunkObjectNames(String bucketName, String ObjectMd5) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == ObjectMd5) {
            return null;
        }
        return listObjectNames(bucketName, ObjectMd5, SORT);
    }

    /**
     * 获取分片名称地址HashMap key=分片序号 value=分片文件地址
     *
     * @param bucketName 存储桶名称
     * @param ObjectMd5  对象Md5
     * @return objectChunkNameMap
     */
    public static Map<Integer, String> mapChunkObjectNames(String bucketName, String ObjectMd5) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == ObjectMd5) {
            return null;
        }
        List<String> chunkPaths = listObjectNames(bucketName, ObjectMd5);
        if (null == chunkPaths || chunkPaths.size() == 0) {
            return null;
        }
        Map<Integer, String> chunkMap = new HashMap<>(chunkPaths.size());
        for (String chunkName : chunkPaths) {
            Integer partNumber = Integer.parseInt(chunkName.substring(chunkName.indexOf("/") + 1, chunkName.lastIndexOf(".")));
            chunkMap.put(partNumber, chunkName);
        }
        return chunkMap;
    }

    /**
     * 合并分片文件成对象文件
     *
     * @param chunkBucKetName   分片文件所在存储桶名称
     * @param composeBucketName 合并后的对象文件存储的存储桶名称
     * @param chunkNames        分片文件名称集合
     * @param objectName        合并后的对象文件名称
     * @return true/false
     */
    @SneakyThrows
    public static boolean composeObject(String chunkBucKetName, String composeBucketName, List<String> chunkNames, String objectName) {
        if (null == chunkBucKetName) {
            chunkBucKetName = chunkBucKet;
        }
//        String objectUrl = getObjectUrl(composeBucketName, objectName, 1);
//        if (objectUrl != null) {
//            return true;
//        }
        List<ComposeSource> sourceObjectList = new ArrayList<>(chunkNames.size());
        for (String chunk : chunkNames) {
            sourceObjectList.add(
                    ComposeSource.builder()
                            .bucket(chunkBucKetName)
                            .object(chunk)
                            .build()
            );
        }
        minioClient.composeObject(
                ComposeObjectArgs.builder()
                        .bucket(composeBucketName)
                        .object(objectName)
                        .sources(sourceObjectList)
                        .build()
        );
        return true;
    }

    /**
     * 合并分片文件成对象文件
     *
     * @param chunkNames 分片文件名称集合
     * @param objectName 合并后的对象文件名称
     * @return true/false
     */
    public static boolean composeObject(List<String> chunkNames, String objectName) {
        return composeObject(chunkBucKet, bucketValue, chunkNames, objectName);
    }

    /**
     * 将分钟数转换为秒数
     *
     * @param expiry 过期时间(分钟数)
     * @return expiry
     */
    private static int expiryHandle(Integer expiry) {
        expiry = expiry * 60;
        if (expiry > 604800) {
            return 604800;
        }
        return expiry;
    }

    public static boolean deleteObject(String fileName) {
        try {
            RemoveObjectArgs build = RemoveObjectArgs.builder()
                    .bucket(bucketValue)
                    .object(fileName)
                    .build();
            minioClient.removeObject(build);
        } catch (Exception e) {
            log.error("删除文件失败minio", e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 永久分享
     *
     * @param fileName 文件名
     * @return 分享地址
     */
    @SneakyThrows
    public static String permanentSharing(String fileName) {
        CopySource source = CopySource.builder().bucket(bucketValue).object(fileName).build();
        CopyObjectArgs build = CopyObjectArgs.builder()
                .bucket("public")
                .object(fileName)
                .source(source)
                .build();
        minioClient.copyObject(build);
        return url + "/public/" + fileName;
    }

    /**
     * 上传文件到公开的空间
     *
     * @param inputStream 文件流
     * @param size        大小
     * @param fileType    后缀
     * @param contentType 类型
     * @return 访问地址
     */
    @SneakyThrows
    public static String uploadFilesToPublicSpace(InputStream inputStream,
                                                  long size,
                                                  String fileType,
                                                  String contentType) {
        String fileName = UUID.randomUUID().toString() + "thisIsTheFileNameAndSuffix" + fileType;
        PutObjectArgs build = PutObjectArgs.builder()
                .bucket("public")
                .object(fileName)
                .stream(inputStream, size, -1)
                .contentType(contentType)
                .build();
        minioClient.putObject(build);
        return url + "/public/" + fileName;
    }
}